feat(v2): land agent-core-v2 engine and kap-server behind experimental flag#1441
feat(v2): land agent-core-v2 engine and kap-server behind experimental flag#1441sailist wants to merge 612 commits into
Conversation
🦋 Changeset detectedLatest commit: 2975437 The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fbb4a3a0c6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const session = this.opts.core.accessor.get(ISessionLifecycleService).get(sessionId); | ||
| if (session === undefined) return undefined; |
There was a problem hiding this comment.
Resume cold sessions before accepting WS subscriptions
When a client reconnects to a session that exists only on disk, such as after a server-v2 restart, the default snapshot path can succeed via the disk reader without materializing the session in ISessionLifecycleService. The following /api/v1/ws subscribe then calls ensureState, but this get() only checks live sessions and returns undefined, so the subscribe ack reports not_found and the socket is never registered; if a later prompt request resumes the session, that client still receives no live turn events. Use resume() here or otherwise create a broadcaster state for cold persisted sessions before returning false.
Useful? React with 👍 / 👎.
| 'file.not_found', | ||
| 'file.too_large', | ||
| 'fs.path_not_found', | ||
| 'fs.permission_denied', | ||
| 'validation.failed', |
There was a problem hiding this comment.
Keep the Kimi error schema exhaustive
The KimiErrorCode union above includes additional codes such as prompt.not_found, session.busy, fs.path_escapes, fs.is_directory, fs.is_binary, fs.too_large, fs.already_exists, fs.too_many_results, fs.grep_timeout, and fs.git_unavailable, but this runtime z.enum omits them. Any error or turn.ended.error payload carrying one of those valid codes from agent-core-v2 will fail eventSchema / sessionEventMessageSchema validation even though the TypeScript type says it is valid, so add the missing literals or derive the schema from a single source.
Useful? React with 👍 / 👎.
|
❌ Nix build failed Hash mismatch in
Please update |
commit: |
…mpty server-v2 binds the main agent via profile:setModel without a cwd, so the profile cwd is empty and planFilePathFor() produced a relative path like plan/<id>.md. The plan-mode guard compares that against the Write tool's workDir-resolved absolute path with strict equality, so it denied every write to the plan file; the relative path also landed in process.cwd() instead of the session workDir, breaking ExitPlanMode. Make the plan path always absolute, falling back to the session workDir (the same root the file tools resolve against) when the profile cwd is empty.
Forward event.session.created to WS clients (it was published on the core bus but never forwarded) and re-emit event.session.status_changed(running) on turn.started. v2 derives session status via ISessionActivity, a pure pull that publishes nothing, so these v1 broadcasts were dropped: clients that did not issue a create never learned the session existed, and the running transition never reached kimi-web, whose Stop button is gated on session.status === 'running'. Mirrors v1's SessionService status emission and isGlobalSessionEvent fan-out.
- add PromptHarness/PromptSession interfaces so the print driver is decoupled from the concrete SDK harness/session - add an in-process agent-core-v2 harness (bootstrap + session + prompt/goal/permission adapters) under cli/v2/ - adapt v2 IEventBus events to the v1 Event union, with unit tests - select the engine in createPromptHarness via isKimiV2Enabled() (KIMI_CODE_EXPERIMENTAL_FLAG), lazy-importing v2 off the default path
- remove unused imports and variables - add void to floating promises - drop redundant return-await - bind unbound method references - replace == null with strict null/undefined checks - add explicit sort comparators and Array.from helpers - rename background_tasks capability to tasks - migrate event sink to event bus in tests
- add a per-connection outbound send buffer in WsConnectionV1; sendFrame enqueues and a flush drains on a 16ms interval or once 64 frames queue - coalesce adjacent volatile assistant/thinking deltas for the same session/agent/turn into one frame, keeping the first offset/seq so client alignment is unchanged - defer flushing while socket.bufferedAmount exceeds a 1MiB high-water mark, merging new deltas into the queued frame instead of growing it - flush remaining frames on close to avoid truncating the tail of a stream - expose flushIntervalMs / maxBatchSize / highWaterMarkBytes via registerWsV1
- bridge split v2 status slices into one combined `agent.status.updated` event via a LegacyStatus derived model at the kap-server edge, so a usage-only event no longer overwrites the live context window with a stale zero - fall back to the configured default model's context window when the agent has no model bound yet, so fresh sessions don't show "0/0" - use `size` (measured + estimated) for the live context token count to mirror v1's `context.tokenCount` - export `defineDerivedModel` / `DerivedModelDef` from agent-core-v2
- add custom onSend/onResponse access logging that records the envelope code - disable Fastify built-in request logging (every response is HTTP 200) - drop pino-pretty; logger always emits newline-delimited JSON - rename dev:server-v2 script to dev:kap-server
The v2 server package is @moonshot-ai/kap-server, but three changesets still referenced the old @moonshot-ai/server-v2 name, which no longer exists in the workspace.
Subagents created via the Agent tool or AgentSwarm fell back to the default `manual` permission mode because CreateAgentOptions offered no way to seed one, so write/execute tool calls inside a subagent prompted for approval even when the parent ran in auto/yolo. Add an optional permissionMode to CreateAgentOptions and have the Agent tool and AgentSwarm pass the caller's current mode when creating a child.
- add reduceContextTranscript in agent-core-v2: keeps the full history across context.apply_compaction (summary marker instead of dropping the prefix) and stops context.undo at compaction summaries, matching v1's transcript view - SnapshotReader: reduce the wire with the transcript reducer instead of the folded live context, so a later undo no longer hides the pre-compaction assistant reply - MessageLegacyService: read the main agent wire log and merge the unflushed live tail instead of the folded IAgentContextMemoryService.get()
- v1 session route handlers and the sessionLegacy adapter resolved the target via ISessionLifecycleService.get, which only sees live (in-memory) sessions. A freshly-opened session is persisted (index + disk) but not live until the first prompt, so commands such as undo / fs:* / skills / :btw / warnings / terminals / profile / questions / approvals / archive reported `session does not exist` (40401). - Resolve via ISessionLifecycleService.resume instead, which cold-loads a persisted session from disk (matches v1's resumeSession); a genuinely missing session still 404s. - Add a cold-load regression for `:undo` and update the skills test for the new cold-load behaviour.
…into kimi-code-v2
…into kimi-code-v2
- add an Agent-scope runtime domain that holds the whole live phase as one discriminated-union field (idle, running, streaming, tool_call, retrying, awaiting_approval, interrupted, ended) - drive it from existing turn, step, delta, tool, retry, interrupt and approval events, edge-triggered with reference-equality dedup so delta bursts do not flood the wire log - carry the phase on the existing agent.status.updated channel and add the AgentPhase type/schema to the protocol (backward compatible)
…into kimi-code-v2
…into kimi-code-v2
- add media-owned `image` config section (`max_edge_px`, `read_byte_budget`) with `KIMI_IMAGE_MAX_EDGE_PX` / `KIMI_IMAGE_READ_BYTE_BUDGET` env bindings (env > config.toml > default) - add Agent-scope `ImageConfigBridge` that pushes the env-resolved section into the image-compress resolver seam on load and on change, so all call sites honor config without per-call wiring - resolve `maxEdge` / read-byte-budget defaults in image-compress via the new seam; keep the support module config-agnostic - apply the read-image byte budget in ReadMediaFile's default downscale path (previously fell back to the 3.75 MB provider ceiling)
Reconcile the parallel goal-mode work on goal-parity with origin's newer kimi-code-v2 architecture. Conflicts resolved by keeping origin's mechanisms and layering parity's behavior on top: - goalService: keep origin's activity-lease continuation launch; graft parity's settleGoalAfterContinuationFailure so a failed relaunch pauses the goal instead of stranding it. Drop the orphaned tokenUsageTotal helper (accounting already charges ctx.usage.output) and its unused TokenUsage import; remove a duplicate MAX_GOAL_COMPLETION_CRITERION_LENGTH. - update-goal: layer parity's outcome-prompt tool results and no-goal guards onto origin's goalIsActive stopBatch refinement. - promptService / fullCompactionService: dedupe imports; keep both DI params (toolSelect and instantiation are each used). - tests: align continuation tests to the lease model (hold the turn lane and mock launchWithLease), swap abortController for signal, and keep parity's token-accounting assertions to match the merged code.
Port v1 #1508 into v2: lower the longest-edge downscale cap back to 2000px (v2 was stuck on the 3000px it had ported from an earlier v1 change) and make it overridable, add the 256 KB read-image byte budget used by ReadMediaFile, and widen the over-budget fallback ladder to [2000, 1000, 768, 512, 384, 256]. - MAX_IMAGE_EDGE_PX 3000 -> 2000, with KIMI_IMAGE_MAX_EDGE_PX env and a config-pushed value resolved via resolveMaxImageEdgePx. - READ_IMAGE_BYTE_BUDGET=256KB with KIMI_IMAGE_READ_BYTE_BUDGET env and resolveReadImageByteBudget; ReadMediaFile's default compress path now uses it (region / full_resolution still honor IMAGE_BYTE_BUDGET). - Test expectations that hard-coded 3000px / 1500px updated to 2000 / 1000 to match the v1 behavior. The [image] config.toml section is intentionally not added: the env vars already cover the override path, and wiring a config section plus a runtime push would add v2-specific scaffolding beyond v1.
…aFile Align with v1: a HEIC/HEIF read is now refused up front with an os-specific conversion command (sips / heif-convert / ImageMagick) so the unsupported format never reaches the provider (which would reject the whole session once it lands in history). The two guidance builders are ported verbatim from v1, and the check sits after the image-capability guard (using IHostEnvironment.osKind). Also give the existing EXIF-rotation test a longer timeout: it does heavy jimp encode/decode and was flaking around the default 5s boundary.
Align with v1: expose `startBtw` on AgentAPI and delegate it to the existing ISessionBtwService (already implemented and DI-registered as SessionBtwService), so the /btw slash command can fork a side-question child agent once the server runs on v2.
- Remove resume-debug.test.ts: a one-off diagnosis script with a hardcoded local path, not a real test. - Move streamTiming.test.ts to app/model/modelImpl.test.ts: it only exercises buildStreamTiming in app/model/modelImpl.ts. - Rename wire/store.test.ts to wire/wireServiceImpl.test.ts to match the module it covers (there is no store.ts in src).
Align with v1: replace the hand-rolled subset validator with v1's Ajv- based implementation (draft-07/2019/2020 + ajv-formats), so tool-call argument validation once again honors the JSON Schema `format` keyword (and the full keyword set), not just the previously hard-coded subset. - args-validator.ts is now byte-identical to v1 (93 lines, replacing the 289-line hand-rolled subset). - Adds ajv@^8.18.0 and ajv-formats@^3.0.1 (same versions as v1) plus the pnpm-lock.yaml update. - The two call sites (compileToolArgsValidator -> validateToolArgs) keep working unchanged; a small test locks in format / required / additionalProperties / subset behavior.
Align with v1: replace the hand-rolled subset validator with v1's Ajv- based implementation (draft-07/2019/2020 + ajv-formats), so tool-call argument validation once again honors the JSON Schema `format` keyword (and the full keyword set), not just the previously hard-coded subset. - args-validator.ts is now byte-identical to v1 (93 lines, replacing the 289-line hand-rolled subset). - Adds ajv@^8.18.0 and ajv-formats@^3.0.1 (same versions as v1) plus the pnpm-lock.yaml update. - The two call sites (compileToolArgsValidator -> validateToolArgs) keep working unchanged; a small test locks in format / required / additionalProperties / subset behavior.
…ist tool source Match the prompt change in 5cc8e52: the CronDelete parameter description and invalid-id error now say "ULID" only (not "ULID or legacy 8-hex"), and the CronList id doc comment likewise. The validation regex is unchanged so loading any legacy 8-hex tasks from disk still works.
…into kimi-code-v2
Pull the 8 new upstream commits on top of the resolved goal-parity merge. Reconciled where upstream now overlaps the parity work: - update-goal: take origin's reimplementation (d9e411f) — no-goal fallbacks return a plain result, not a tool error. Dropped parity's contradicting "fail as a tool error" test; origin's goal-tools.test.ts covers the non-error behavior. - promptService: keep parity's steer-buffer semantics (gap G24) — steers survive a cancelled/failed turn and flush into the next turn. Did not re-adopt origin's turn-result observation that discards them; kept parity's compaction-defer feature. Updated the retained test to the new LoopRunResult shape. - goalService: keep the imports still used by the surviving goal-outcome code; drop TurnResult (no longer referenced after the upstream merge). - goal.test.ts: adopt the reorganized test path (test/agent/goal) and its relative imports; keep the activity-lease import used by the continuation tests.
Fast catch-up merge; no conflicts. Brings in tool-args JSON Schema format validation (adds ajv/ajv-formats deps), cron tool-source cleanup, and test file tidy. No overlap with the goal-parity work.
It round-tripped restore over a hardcoded local dataset (kimi-code-mini-bench/.vitest-results) that is not in the repo, so it vacuously passed everywhere else. Removed at the original author's request.
…test The fuzz-style invariant test now drives the full v1 fallback ladder ([2000, 1000, 768, 512, 384, 256]) for over-budget inputs, which takes longer than the default 5s boundary in this environment. Give it 30s.
- agent/task/manager.test.ts -> taskManager.test.ts (no manager.ts in agent/task; disambiguate from taskService.test.ts). - app/cron/persist.test.ts -> cronTaskPersistenceService.test.ts (mirrors the module it covers; agent/task/persist.test.ts mirrors agent/task/persist.ts, so it is left as-is). - agent/contextMemory/message.test.ts -> message-history.test.ts (its describe is 'message history (IAgentContextMemoryService)'; the dir already names the domain).
- sessionLifecycle: get/list no longer return a session whose cold resume is still in flight, so callers never observe a half-initialized handle; resume remains the way to await a fully restored handle - messageLegacy: reduce the transcript from the main agent's in-memory wire journal instead of re-reading wire.jsonl; AgentWireRecordService now keeps the journal current with live dispatch so cold and live sessions both read a consistent, full transcript
micro-compaction only exists in legacy agent-core; v2 has no such mechanism. Update two comments that still cited it: - fullCompactionService: the real reason not to project here is that llmRequester already projects once. - swarmService: context.spliced consumers no longer include micro-compaction bookkeeping.
…into kimi-code-v2
…into kimi-code-v2
Since d5e1d76 every wire append rides the async persist queue whenever a blob service is registered, but AgentWireRecordService.flush() only awaited the log store. Callers - including the session-close path - could complete a flush while records were still in flight on the queue, and record-order assertions in tests raced it. Await the wire service flush, which drains the persist queue, before flushing the log.
…ontinuation driver The per-turn-boundary reminder test predates the goal continuation driver and never passed: its second explicit prompt raced the auto-launched continuation turn, which correctly holds the turn lane. Treat the continuation turn as the second boundary and end it deterministically by completing the goal through UpdateGoal, keeping the once-per-boundary (never per-step) assertion.
…exhausted Previously a goal whose hard budget was reached mid-turn was only flipped to blocked while the turn kept running unbounded: the loop continues unconditionally after a tool-calls step, and steer flushes or Stop hooks could extend the turn indefinitely past the budget. Now, when the over-budget step requested tool calls, a goal_budget_stop system reminder is appended after the tool results telling the model the goal is blocked (resumable via /goal resume), to stop immediately, that further tool calls will be rejected, and to write a brief final status message. The model gets exactly one grace step, during which tool calls are answered with a soft rejection instead of executing. After the grace step - or when the over-budget step had no pending tool calls - a new AfterStepContext.stopTurn flag ends the turn, honored by the loop with precedence over tool_calls and hook-set continue so nothing can extend past the stop. The grace grant also respects maxStepsPerTurn so it can never turn a budget stop into a max-steps turn failure. Turn launch is gated as well: a prompt arriving while the active goal is already over budget (e.g. after resuming an exhausted goal) blocks the goal before the turn is marked goal-driven, so turnsUsed no longer drifts, no spurious goal_continued telemetry fires, and the prompt runs as an ordinary turn with the blocked-goal note injected. This deliberately diverges from agent-core v1, which hard-stops with zero grace and answers a prompt on an exhausted goal with a synthetic model-less turn: budget overshoot is now bounded at one closing step in exchange for consumed tool results and a user-facing wrap-up message.
Related Issue
N/A — integration PR that lands the long-lived
kimi-code-v2branch; see Problem below.Problem
The v2 line — the DI × Scope based
agent-core-v2engine, thekap-serverthat hosts it, and theminidbread model — has been built up on the long-livedkimi-code-v2integration branch. This PR merges that branch intomain.The v2 engine and server are not the default runtime yet. Both the
kimi -pprompt path andkimi server runroute to v2 only whenKIMI_CODE_EXPERIMENTAL_FLAGis set, and the v2 module graph is lazy-imported so it stays off the default path. With the flag unset, the CLI keeps the v1 engine and@moonshot-ai/server.What changed
Scope: 1,207 files, +192,653 / −285. The bulk is three new packages; everything else is CLI wiring, e2e coverage, skills, and small v1 touch-ups.
1. agent-core-v2: the new DI × Scope engine (
packages/agent-core-v2, +146k, 872 files)agent-core-v2package: a DI scope engine with an explicit agent / session lifecycle.ReplayTimelineModel;IEventBusandOp.toEventalongsidewire.signal; context writes routed through ops and declared tool delivery.wire.jsonl); flattened agent hierarchy with swarm lifted to session.Modelgod-object and protocol domains; kosong vendored as the internalllmProtocol/kosongimplementation (drops the@moonshot-ai/kosongand@moonshot-ai/kaosdependencies).rglocator/runner, AGENTS.md hierarchy loading.2. kap-server: the v2 server (
packages/kap-server, +26k, 139 files)@moonshot-ai/kap-serverpackage — the Kimi Code server backed byagent-core-v2(codenamed "server-v2").agent-core-v2sessions over REST (/api/v1, ~25 route modules) and WebSocket (/api/v1/wswith seq/epoch watermark and resync), and serves the web assets./openapi.jsonvia@fastify/swagger, and v2 session export (diagnostic zip archives).before_idpagination cursor, treat loopback origins as same-origin for WS upgrade, and align MCP media output with v1.3. minidb: embedded read model (
packages/minidb, +13k, 77 files)@moonshot-ai/minidbpackage — a pure-Node.js embedded KV database (Redis-style in-memory KV with SQLite-style WAL/snapshot persistence, secondary indexes, TTL), used as the session-index read model.4. CLI wiring (
apps/kimi-code, +762, 19 files)kimi -pandkimi server runselect v2 vs. v1 viaKIMI_CODE_EXPERIMENTAL_FLAG(lazy import); v1 stays the default.cli/v2/*) and theprompt-sessionabstraction shared by both engines; update TUI adapters to handle v2 events.5. Tooling, e2e and docs
packages/server-e2e: typed v2ServerClientSDK with a drift test and e2e coverage..agents/skills: add theagent-core-devandwrite-testsskills plus other skill updates; add the dependency-graph dev viewer.GOAL.md: design doc for the goal-mode feature split.packages/protocol,packages/acp-adapter,packages/agent-core,packages/server,packages/node-sdk,apps/kimi-web, thehash-importsbuild loaders,flake.nix, and changesets.Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.